package com.yc.spring.ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class AnnotationConfigApplicationContext {

    protected Class<?> configClass;
    // ConcurrentHashMap 线程安全的 Map集合
    protected Map<String, BeanInfo> ioc = new ConcurrentHashMap<>();
    Object configuration;

    public AnnotationConfigApplicationContext(Class<?> configClass) {
        this.configClass = configClass;
        try {
            configuration = configClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("配置类创建失败!", e);
        }
        // 判断是否是 Spring 配置类
        if (configClass.getAnnotation(Configuration.class) == null) {
            throw new RuntimeException(configClass + " 不是配置类");
        }

        // 获取组件对象, 存入IOC
        // 1. 配置类中所有加了 @Bean 的方法
        Method[] methods = configClass.getDeclaredMethods();
        for (Method method : methods) {
            // 判断 @Bean
            Bean bean = method.getAnnotation(Bean.class);
            if (bean != null) {
                // 获取 bean 的 id
                String[] names;
                if (bean.value().length > 0) {
                    names = bean.value();
                } else {
                    names = new String[]{method.getName()};
                }
                // 获取 bean 对象, 暂时不考虑 method 带参数的情况
                if (method.getParameterCount() > 0) {
                    continue;
                }
                Object obj;
                BeanInfo beanInfo;
                try {
                    method.setAccessible(true);
                    obj = method.invoke(configuration);
                    boolean isPrimary = method.getAnnotation(Primary.class) != null;
                    beanInfo = new BeanInfo(obj, isPrimary);
                } catch (Exception e) {
                    throw new RuntimeException("创建对象失败: " + method, e);
                }
                // 将对象存入容器
                for (String name : names) {
                    ioc.put(name, beanInfo);
                }
            }
        }

        // 2. 扫描包路径中所有加了 组件注解 的类
        ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
        if (componentScan != null) {
            String[] packages = componentScan.value();
            // 包扫描方式: 扫描 classes 目录
            for (String aPackage : packages) {
                // 包路径 => 文件路径
                String classPath = aPackage.replaceAll("\\.", "/");
                // com/yc/spring/vote
                URL url = getClass().getClassLoader().getResource(classPath);
                // url => 文件对象
                String decode = null;
                try {
                    decode = URLDecoder.decode(url.getPath(), "utf-8");
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException("解码失败:" + url.getPath(), e);
                }
                File scanRootPath = new File(decode);
                System.out.println(scanRootPath.getAbsoluteFile());
                List<File> classFiles = new ArrayList<>();
                scanBeanClass(scanRootPath, classFiles);
                classFiles.forEach(System.out::println);
                // 获取项目根目录
                url = getClass().getClassLoader().getResource("");
                try {
                    decode = URLDecoder.decode(url.getPath(), "utf-8");
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException("解码失败:" + url.getPath(), e);
                }
                // test-classes
                File projectRootPath = new File(decode);
                projectRootPath = new File(projectRootPath.getParentFile(), "classes");
                for (File classFile : classFiles) {
                    classPath = classFile.getAbsolutePath();
                    classPath = classPath.replace(projectRootPath.getAbsolutePath(), "");
                    classPath = classPath.replaceAll("\\.class", "");
                    classPath = classPath.replaceAll("\\\\", ".");
                    classPath = classPath.substring(1);
                    // 扩展
                    try {
                        // 1.使用类路径获取类对象
                        Class cls = Class.forName(classPath);
                        // 2.判断该类是否是 组件  (4个组件注解)
                        if (cls.getAnnotation(Component.class) != null
                                || cls.getAnnotation(Controller.class) != null
                                || cls.getAnnotation(Service.class) != null
                                || cls.getAnnotation(Repository.class) != null) {
                            // 3.如果是, 则使用反射测试件该类 bean
                            Object bean = cls.newInstance();
                            //     4. 获取 bean 的 id 1) 组件的value属性, 2)类名首字母小写
                            String name = cls.getSimpleName();
                            name = name.substring(0, 1).toLowerCase() + name.substring(1);
                            //     5. 将 bean 存入 ioc
                            ioc.put(name, new BeanInfo(bean, false));
                        }
                    } catch (Exception e) {
                        System.out.println("创建 classPath 失败, 原因:" + e.getMessage());
                    }
                }

                // 所有的组件对象已经进入 ioc 容器
                // 实现自动装配 autowire
                for (BeanInfo beanInfo : ioc.values()) {
                    Class<?> beanClass = beanInfo.bean.getClass();
                    for (Field field : beanClass.getDeclaredFields()) {
                        if (field.getAnnotation(Resource.class) != null) {
                            field.setAccessible(true);
                            // 如果该属性已经注入过了, 则继续下一个循环
                            try {
                                if (field.get(beanInfo.bean) != null) {
                                    continue;
                                }
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException("获取属性值错误!", e);
                            }
                            // 基于 byName 自动注入
                            Resource resource = field.getAnnotation(Resource.class);
                            String name = resource.name();
                            if (name.isEmpty()) {
                                name = field.getName();
                            }
                            Object bean = getBean(name);
                            if (bean != null) {
                                try {
                                    field.set(beanInfo.bean, bean);
                                } catch (IllegalAccessException e) {
                                    throw new RuntimeException("自动注入对象失败" + field, e);
                                }
                            }
                        } else if (field.getAnnotation(Autowired.class) != null) {
                            // 基于 byType 方式
                            Class<?> type = field.getType();
                            Object bean = getBean(type);
                            // 暂时不处理多个 bean 的请求
                            try {
                                field.setAccessible(true);
                                field.set(beanInfo.bean, bean);
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException("自动注入对象失败:" + field, e);
                            } catch (RuntimeException e) {
                                if (e.getMessage().contains("包含多个bean")) {
                                    // 扩展:
                                    // 获取限定词注解
                                    // 从限定词注解中获取 name
                                    // 通过 name 获取 bean
                                    // 完成注入
                                } else {
                                    throw e;
                                }
                            }
                        }
                    }
                }
            }
        }


        ioc.forEach((key, beanInfo) -> {
            System.out.printf("%s   %s\n", key, beanInfo);
        });
    }

    public void scanBeanClass(File scanPath, List<File> classFiles) {
        for (File file : scanPath.listFiles()) {
            if (file.isDirectory()) {
                scanBeanClass(file, classFiles);
            } else if (file.isFile()) {
                if (file.getName().endsWith(".class")) {
                    classFiles.add(file);
                }
            } else {
                // 文集不存在, 新建的文件
            }
        }
    }


    // 通过id或name返回bean
    public Object getBean(String idOrName) {

        return ioc.get(idOrName) == null ? null : ioc.get(idOrName).bean;
    }

    // 通过类对象或name返回bean
    public <T> T getBean(Class<T> cls) {
        List<BeanInfo> beans = new ArrayList<>();
        for (BeanInfo beanInfo : ioc.values()) {
            // 咱不考虑 cls 的子类 bean
            if (cls.equals(beanInfo.bean.getClass())) {
                beans.add(beanInfo);
            }
        }
        if (beans.isEmpty()) {
            throw new RuntimeException("没有该类型的bean: " + cls);
        } else if (beans.size() == 1) {
            return (T) beans.get(0).bean;
        } else {
            for (BeanInfo beanInfo : beans) {
                if (beanInfo.isPrimary) {
                    return (T) beanInfo.bean;
                }
            }
            throw new RuntimeException(cls + "包含多个bean: " + beans);
        }
    }

    public static class BeanInfo {
        public Object bean;
        public boolean isPrimary;

        public BeanInfo(Object bean, boolean isPrimary) {
            this.bean = bean;
            this.isPrimary = isPrimary;
        }
    }

}


